package sagroup.dracots;

import lime2.TupleID;
import lime2.Location;
import java.util.HashMap;

/**
 * ReplicaRequestRegistrar maintains the replication info of a LimeTupleSpace. If some
 * remote hosts register replica requests here, a replica request info will be created
 * in this class to provide support for further replica updating.
 * 
 * 
 * @author Huang Xiaodong
 */
public class ReplicaRequestRegistrar {
	/**
	 * HashMap that keeps the replication info. A two-level HashMap, that is,
	 * HashMap<VID, HashMap<replicaLocation, expectedVersion>>.
	 */
	private HashMap<TupleID, HashMap<Location, Long>> ReplicaMap;

	/**
	 * Constructor
	 */
	public ReplicaRequestRegistrar() {
		ReplicaMap = new HashMap<TupleID, HashMap<Location, Long>>();
	}

	public TupleID[] getVIDs() {
		TupleID[] ret = null;
		if (ReplicaMap != null && ReplicaMap.keySet() != null) {
			ret = new TupleID[ReplicaMap.keySet().size()];
			ret = ReplicaMap.keySet().toArray(ret);
		}
		return ret;
	}

	public Location[] getReplicaLocations(TupleID vid) {
		Location[] ret = null;
		if (ReplicaMap != null && ReplicaMap.get(vid) != null) {
			HashMap<Location, Long> inner = ReplicaMap.get(vid);
			if (inner.keySet() != null) {
				ret = new Location[inner.keySet().size()];
				ret = inner.keySet().toArray(ret);
			}
		}
		return ret;
	}

	/**
	 * Show the contents of replicaRegistrar.
	 * 
	 * @author Huang Xiaodong
	 */
	public void ShowAll() {
		System.out.println("Show the contents of replicaRegistra.");
		for (TupleID vid : ReplicaMap.keySet()) {
			System.out.println("VID = " + vid + " ,");
			HashMap<Location, Long> inner = ReplicaMap.get(vid);
			if (inner != null) {
				for (Location loc : inner.keySet())
					System.out.println("   Location = " + loc
							+ " and Expected Version = " + inner.get(loc));
			}
		}
	}

	/**
	 * @Override
	 */
	public String toString() {
		return "ReplicaMap=" + ReplicaMap.toString();
	}

	/**
	 * Register a specified replication.
	 * 
	 * @param vid
	 * @param replicaLoc
	 * @param expectedVersion
	 * @author Huang Xiaodong
	 */
	public void registerReplica(TupleID vid, Location replicaLoc,
			Long expectedVersion) {
		HashMap<Location, Long> inner = ReplicaMap.get(vid);
		if (inner == null) {
			inner = new HashMap<Location, Long>();
			ReplicaMap.put(vid, inner);
		}
		Long ver = inner.get(replicaLoc);
		if (ver == null)
			inner.put(replicaLoc, expectedVersion);
		else
			// Already registered
			updateReplica(vid, replicaLoc, expectedVersion);
	}

	/**
	 * Deregister a replication with specified vid and replicaLocation.
	 * 
	 * @param vid
	 * @param replicaLoc
	 * @return the expected version of the deregistered replication
	 * @author Huang Xiaodong
	 */
	public Long deregisterReplica(TupleID vid, Location replicaLoc) {
		Long ret = null;
		HashMap<Location, Long> inner = ReplicaMap.get(vid);
		if (inner != null) {
			ret = inner.remove(replicaLoc);
			if (inner.isEmpty())
				ReplicaMap.remove(vid);
		}
		return ret;
	}

	/**
	 * Deregister replications with specified vid.
	 * 
	 * @param vid
	 * @param replicaLoc
	 * @author Huang Xiaodong
	 */
	public Location[] deregisterReplica(TupleID vid) {
		HashMap<Location, Long> inner = ReplicaMap.remove(vid);
		if (inner != null && inner.keySet() != null) {
			Object[] obj_loc = inner.keySet().toArray();
			Location[] ret = new Location[obj_loc.length];
			for (int i = 0; i < obj_loc.length; ++i)
				ret[i] = (Location) obj_loc[i];
			return ret;
		} else
			return new Location[0];
	}

	/**
	 * Update the version of a replication specified VID, replicaLocation.
	 * 
	 * @param vid
	 * @param replicaLoc
	 * @param newVersion
	 * @author Huang Xiaodong
	 */
	public void updateReplica(TupleID vid, Location replicaLoc, Long newVersion) {
		HashMap<Location, Long> inner = ReplicaMap.get(vid);
		if (inner == null || inner.get(replicaLoc) == null)
			registerReplica(vid, replicaLoc, newVersion);
		else if (inner.get(replicaLoc) < newVersion) {
			inner.remove(replicaLoc);
			inner.put(replicaLoc, newVersion);
		}
	}

	/**
	 * Update the version of all replications with specified VID.
	 * 
	 * @param vid
	 * @param newVersion
	 * @return all the Locations that have replication of updated tuple
	 * @author Huang Xiaodong
	 */
	public Location[] updateReplica(TupleID vid, Long newVersion) {
		HashMap<Location, Long> inner = ReplicaMap.get(vid);
		Location[] ret = null;
		if (inner != null) {
			if (inner.isEmpty()) {
				ReplicaMap.remove(vid);
			} else {
				if (inner.keySet() != null) {
					Object[] obj_loc = inner.keySet().toArray();
					ret = new Location[obj_loc.length];
					for (int i = 0; i < obj_loc.length; ++i)
						ret[i] = (Location) obj_loc[i];
					for (Location replicaLoc : inner.keySet()) {
						if (inner.get(replicaLoc) < newVersion) {
							//inner.remove(replicaLoc);
							inner.put(replicaLoc, newVersion);
						}
					}
				}
			}
		}
		return ret;
	}

	/**
	 * Get the expected version of replications that specified vid and location
	 * 
	 * @param vid
	 * @param replicaLoc
	 * @return the expected version
	 * @author Huang Xiaodong
	 */
	public Long getReplicaVersion(TupleID vid, Location replicaLoc) {
		HashMap<Location, Long> inner = ReplicaMap.get(vid);
		return (inner == null ? null : inner.get(replicaLoc));
	}
}